﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Platform.ObjectModel
{
	/// <summary>
	/// Representa o identificador de um documento
	/// x190bd
	/// </summary>
	public class DocumentToken 
	{
		private static readonly char[] tokenChars = DocumentTokenHelper.TokenChars();

		internal DocumentToken( string value )
		{
			this.Value = value;
		}

		/// <summary>
		/// Obtém o valor do token
		/// </summary>
		public string Value
		{
			get;
			private set;
		}

		public string ReversedValue
		{
			get { return this.Value.Reverse(); }
		}

		/// <summary>
		/// Obtém o tamanho do token
		/// </summary>
		[Obsolete( "Não faz sentido ter essa propriedade define o tamanho máximo de um token")]
		public static int TokenLength
		{
			get { return 7; }
		}

		/// <summary>
		/// Obtém o último token
		/// </summary>
		[ Obsolete("Não existe o último token")]
		public static DocumentToken Max
		{
			get
			{
				StringBuilder token = new StringBuilder( TokenLength );
				for ( int i = 0; i < TokenLength; i++ )
					token.Append( tokenChars.Last() );

				return new DocumentToken( token.ToString() );
			}
		}

		/// <summary>
		/// Obtém o primeiro token
		/// </summary>
		[Obsolete( "Não existe o primeiro ou o menor token.")]
		public static DocumentToken Min
		{
			get
			{
				StringBuilder token = new StringBuilder( TokenLength );
				for ( int i = 0; i < TokenLength; i++ )
					token.Append( tokenChars.First() );

				return new DocumentToken( token.ToString() );
			}
		}

		/// <summary>
		/// Obtém os caracteres que fazem parte de um token
		/// </summary>
		public static char[] ValidTokenChars
		{
			get { return DocumentToken.tokenChars; }
		}

		
		/// <summary>
		/// Realiza a criação de um novo token
		/// </summary>
		/// <returns></returns>
		public static DocumentToken NewToken()
		{
			List<char> token = new List<char>();

			int processId = System.Diagnostics.Process.GetCurrentProcess().Id;  //4
			long timeStamp = DateTime.Now.ToBinary();							//8
			ushort randomValue = DocumentTokenHelper.GetRandomID();
			ushort randomValue2 = DocumentTokenHelper.GetRandomID2();

			token.AddRange( DocumentTokenHelper.ConvertToChar( randomValue ).Reverse() );
			token.AddRange( DocumentTokenHelper.ConvertToChar( randomValue2 ).Reverse() );
			token.AddRange( DocumentTokenHelper.ConvertToChar( processId ).Reverse() );
			token.AddRange( DocumentTokenHelper.ConvertToChar( timeStamp ).Reverse() );

			return new DocumentToken( new String( token.ToArray() ) );
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="sequence"></param>
		/// <returns></returns>
		public static DocumentToken NewToken( long sequence )
		{
			if ( sequence <= 0 )
				throw new ArgumentException( "Somente número positivos podem ser utilizados para criar um novo token." );

			//int tokenLen = 7;

			int charsLen = DocumentToken.tokenChars.Length;
			char[] token = new char[ 7 ];

			int tokenIndex = token.Length - 1;
			long value = sequence;
			while ( value >= charsLen )
			{
				short charsIndex = ( short )( value % charsLen );
				value /= charsLen;

				token[ tokenIndex-- ] = DocumentToken.tokenChars[ charsIndex ];
			}

			token[ tokenIndex-- ] = DocumentToken.tokenChars[ value ];

			// Faz o padding do restante do token, preenchendo com '0'
			while ( tokenIndex >= 0 )
				token[ tokenIndex-- ] = DocumentToken.tokenChars[ 0 ];

			return new DocumentToken( new String( token ) );
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="guid"></param>
		/// <returns></returns>
		public static DocumentToken NewToken( Guid guid )
		{
			var tokenChars = guid.ToByteArray().Select( b => DocumentTokenHelper.ConvertToChar( b ).Single() );
			return new DocumentToken( new String( tokenChars.ToArray() ) );
		}

		/// <summary>
		/// Retorna o total de tokens que podem ser gerados
		/// </summary>
		/// <returns></returns>
		[Obsolete]
		public static long GetMaxTokenCount()
		{
			long total = 1;
			int len = TokenLength;

			while ( len-- > 0 )
				total *= TokenLength;

			return total;
		}

		#region [ members override Object ]

		public override bool Equals( object obj )
		{
			DocumentToken token = obj as DocumentToken;

			if ( token == null )
				return false;
			else
				return this.Value.Equals( token.Value );
		}

		public override int GetHashCode()
		{
			return this.ToString().GetHashCode();
		}

		public override string ToString()
		{
			return this.Value;
		}

		#endregion

		/// <summary>
		/// Converte a string <paramref name="value"/> em um token. Verifica se todas
		/// </summary>
		/// <param name="value"></param>
		/// <param name="token"></param>
		/// <returns></returns>
		public static bool TryParse( string value, out DocumentToken token )
		{
			token = null;

			if ( !String.IsNullOrEmpty( value ) )
			{
				var ranges = DocumentToken.ValidTokenChars.Select( c => ( int )c ).SplitInRanges().ToList();
				var valueArray = value.ToUpper().Select( c => ( int )c );

				// Verifica se todos os caracters são válidos
				bool isValid = valueArray.All( c => ranges.Any( r => r.IsInRange( c ) ) );

				if ( isValid )
					token = new DocumentToken( value );
				return isValid;
			}
			else
				return false;

		}
	}
}
